Android UI自动化-InputEvent Record

在工作过程中,部分项目收到客户反馈,出现偶发性Crash,为了尽可能的复现客户问题,需要在程序内模拟用户输入,比如拍照,切换模式,切换分辨率,开关HDR模式等,之前有大概介绍过,目前在Android上做这类UI测试的几种主要方式:

  • 利用espresso,uiautomator等UI测试框架去寻找特定的View,并执行对应操作。优点是:应用场景全面,包括普通的点击滑动,还有自动输入等高级操作;大概率可以忽略屏幕适配的问题;可以在user版本运行。缺点:学习成本较高;复杂场景脚本维护工作了较大。
  • 利用adb shell input x y 的方式去模拟用户点击,组合操作可以通过脚本的方式完成。优点:方便实用;快。缺点:复杂场景脚本的设计会比较艰难,需要连接PC。
  • 因为Android也是类Linux系统,可以模拟鼠标等驱动事件,直接往Linux底层写事件。优点:可以进行事件等录制,适应场景极多;快速。缺点:学习成本较高。

考虑到我们的场景,以及后面还有类似的项目问题,所以我们选择了第三种,可以录制用户操作,然后按照录制的操作开始循环执行的方式。

1.原理分析

1.1.Android界面点击事件流程

用户在屏幕上点击一下后,程序里面的OnClickListener是怎样收到这个事件的。大致流程如下 :

用户点击 -> (硬件驱动部分)硬件产生一个中断 -> /dev/input/event* 写入一个相应的信号 -> jni部分 android循环读取/dev/input/event的事件 -> 分发给WindowManagerServer -> 最后再发到相应的ViewGroup和View。

这里我们就可以通过往/dev/input/event* 写信号的方式,来达到模拟事件的目的。

1.2.触摸协议分析

在2,1中,我们知道可以通过模拟的方式来达到我们模拟事件的目的,但是有个问题在于,事件的种类分为很多种,比如物理按键事件,触摸屏事件,蓝牙等,此处我们重点分析触摸屏事件。

触摸协议分单点触摸和多点触摸。以单点触摸为例,打开手机,同样关闭自动旋屏,输入adb shell getevent,鼠标点击一下屏幕,要足够快,不然数据太多。得到输出和下面类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[min@bogon:] ~ $ adb shell getevent
could not get driver version for /dev/input/mouse0, Not a typewriter
could not get driver version for /dev/input/mice, Not a typewriter
add device 1: /dev/input/event6
name: "huawei,ts_kit"
add device 2: /dev/input/event0
name: "soundtrigger_input_dev"
add device 3: /dev/input/event2
name: "hisi_on"
add device 4: /dev/input/event3
name: "minibugreport_key"
add device 5: /dev/input/event4
name: "fingerprint"
add device 6: /dev/input/event5
name: "hi3xxx_hi6405_card Headset Jack"
add device 7: /dev/input/event1
name: "hisi_gpio_key"
/dev/input/event6: 0003 003a 00000033
/dev/input/event6: 0003 0035 00000251
/dev/input/event6: 0003 0036 000002f2
/dev/input/event6: 0003 0039 00000000
/dev/input/event6: 0000 0002 00000000
/dev/input/event6: 0001 014a 00000001
/dev/input/event6: 0000 0000 00000000
/dev/input/event6: 0003 003a 00000033
/dev/input/event6: 0003 0035 00000251
/dev/input/event6: 0003 0036 000002f2
/dev/input/event6: 0003 0039 00000000
/dev/input/event6: 0000 0002 00000000
/dev/input/event6: 0000 0000 00000000
/dev/input/event6: 0003 003a 00000034
/dev/input/event6: 0003 0035 00000251
/dev/input/event6: 0003 0036 000002f2
/dev/input/event6: 0003 0039 00000000
/dev/input/event6: 0000 0002 00000000
/dev/input/event6: 0000 0000 00000000
/dev/input/event6: 0003 003a 00000034
/dev/input/event6: 0003 0035 00000251
/dev/input/event6: 0003 0036 000002f2
/dev/input/event6: 0003 0039 00000000
/dev/input/event6: 0000 0002 00000000
/dev/input/event6: 0000 0000 00000000

可单点触摸的协议每次点击会写6条信号,按照这个的格式:type code value,具体type含义,在本段末尾有贴出,结合type说明分析单点触摸事件如下:

1
/dev/input/event0: 0003 0000 00000117 EV_ABS ABS_X 0x117

触摸点的x坐标

1
/dev/input/event0: 0003 0001 0000020f EV_ABS ABS_Y 0x20f

触摸点的y坐标

1
/dev/input/event0: 0001 014a 00000001 EV_KEY BTN_TOUCH 1

touch down

1
/dev/input/event0: 0000 0000 00000000 EV_SYN 0 0

同步信号量

1
/dev/input/event0: 0001 014a 00000000 EV_KEY BTN_TOUCH 0

touch up

1
/dev/input/event0: 0000 0000 00000000 EV_SYN 0 0

同步信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
其中,type定义如下:
/*
* Event types
*/
#define EV_SYN 0x00
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)

常用的是EV_KEY, EV_REL, EV_ABS, EV_SYN分别对应按键, 相对坐标, 绝对坐标, 同步事件。
EV_SYN则表示一组完整事件完成上报,需要处理。

1.3.可行性验证

那么具体等事件分析已经结束,我们是否可以将这些事件写入对应的/dev/input/event* 来达到我们模拟事件点击的目的呢?进入开发者模式,打开show touches 和pointer locations勾上后,可以看到点击的轨迹,再结合adb shell sendevent输入以上消息,可以看到屏幕上出现点击效果。

实践证明我们是可以模拟的点击事件的,而且已经有现成的getevent 和 sendevent的方式可以很方便的使用。但是实际上,当我们使用这样的方式在Lenovo,Vivo的部分机型上验证的时候发现,getevent得到响应的事件输出的速度让人捉急,这中间产生的耗时对我们的UI测试来说其实很难忍受。

2.实践改造

基于getevent和setevent的原理,源码路径, 我们实现了一套可以从/dev/input/event*读取并写入的Kit,有兴趣的可以邮件联系我获得源码

备注:目前已经在MI 6,Lenovo zap,Vivo X21,HW nova4上验证通过。


参考
android跨进程事件注入——直接往linux底层写事件

Android getevent命令分析Input事件